package jsr292.weaver.opt;

import java.io.PrintWriter;
import java.util.HashMap;

import jsr292.java.lang.invoke.MethodHandle;
import jsr292.weaver.Capabilities;
import jsr292.weaver.OnlineWeaver;
import jsr292.weaver.Utils;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;

public class OptimizerWeaver extends ClassVisitor {
    private final Class<?> classBeingRedefined;
    private final HashMap<String, OptimizerMethodInfo> methodInfoMap;
    private final OptimizerBoundMap boundMap;
    
    private String ownerClassName;

    public OptimizerWeaver(ClassVisitor cv, Class<?> classBeingRedefined, HashMap<String, OptimizerMethodInfo> methodInfoMap) {
	super(Opcodes.ASM4, cv);
	this.classBeingRedefined = classBeingRedefined;
	this.boundMap = new OptimizerBoundMap();
	this.methodInfoMap = methodInfoMap;
    }

    @Override
    public void visit(int version, int access, String name, String signature,
	    String superName, String[] interfaces) {
	ownerClassName = name;
	super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
	MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

	OptimizerMethodInfo methodInfo = methodInfoMap.get(name);
	if (methodInfo == null)
	    return mv;

	mv.visitCode();

	OptimizerContext context = new OptimizerContext(classBeingRedefined, methodInfo.cs, boundMap, mv);
	OptimizerVarAllocator allocator = new OptimizerVarAllocator(true, 0, 0, Type.getArgumentTypes(desc));
	if (methodInfo.kind == OptimizerMethodInfo.Kind.METHOD_HANDLE_INVOKE) {
	    generateMethodHandleInvoke(context, allocator, ownerClassName, name + "mh", Type.getReturnType(desc), methodInfo.mh);
	    
	    // ask for deoptimization
	    mv.visitLdcInsn(Type.getObjectType(ownerClassName));
	    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/opt/Optimizer",
	            "deoptimize", "(Ljava/lang/Class;)V");
	    
	    // load method handle
	    mv.visitVarInsn(Opcodes.ALOAD, 0);
	    
	    // remove first parameter type MethodHandle;
	    desc = '('+desc.substring(1 + Utils.JSR292_METHOD_HANDLE_DESC.length());
	    OnlineWeaver.generateGenericCall(mv, desc, 1);
        } else {
            generateIndyInvoke(context, allocator, methodInfo.mh);
        }
	
	Type returnType = Type.getReturnType(desc);
	mv.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
	
	mv.visitMaxs(0, 0);
	mv.visitEnd();

	return null;
    }
    
    @Override
    public void visitEnd() {
        super.visitEnd();
        
        if (boundMap.isEmpty()) {
            return;
        }
        ClassLoader classLoader = classBeingRedefined.getClassLoader();
        if (classLoader == null) {  //XXX need to be revised
            classLoader = ClassLoader.getSystemClassLoader();
        }
        String className = boundMap.getOwnerClassName().replace('/', '.');
	byte[] byteArray = boundMap.toByteArray();
	//new ClassReader(byteArray).accept(new TraceClassVisitor(new PrintWriter(System.out)), 0);
	//CheckClassAdapter.verify(new ClassReader(byteArray), classLoader, true, new PrintWriter(System.out));
        Capabilities.define(classLoader, className, byteArray);
    }

    public static void generateMethodHandleInvoke(OptimizerContext context, OptimizerVarAllocator allocator, String methodHandleSlotOwnerClassName, String methodHandleSlotName, Type returnType, MethodHandle methodHandle) {
        MethodVisitor mv = context.mv();
	mv.visitFieldInsn(Opcodes.GETSTATIC, methodHandleSlotOwnerClassName,
		methodHandleSlotName, Utils.JSR292_METHOD_HANDLE_DESC);
	
	allocator.getVar(0).load(mv);

	Label cacheMissLabel = new Label();
	mv.visitJumpInsn(Opcodes.IF_ACMPNE, cacheMissLabel);
	
	OptimizerVarAllocator subAllocator = new OptimizerVarAllocator(allocator, allocator.allowReplaceInPlace);
	subAllocator.drop(0, 1);
	methodHandle.weave(context, subAllocator);
	
	mv.visitInsn(returnType.getOpcode(Opcodes.IRETURN));

	mv.visitLabel(cacheMissLabel);
    }

    
    private static void generateIndyInvoke(OptimizerContext context, OptimizerVarAllocator allocator, MethodHandle methodHandle) {
        methodHandle.weave(context, allocator);
    }
}
